import { DBComponent } from "../../../../Model/Server/Module/DB/DBComponent";
import { ObjectSystemAttribute, FriendOf, ExpandOf, GetTypeOf, ObjectList2BsonList, Object2Bson } from "../../../../../Decorator/Decorator";
import { Awake3System } from "../../../../../Core/Module/EventSystem/IAwakeSystem";
import { DestroySystem } from "../../../../../Core/Module/EventSystem/IDestroySystem";
import { Collection, Filter, MongoClient, OptionalUnlessRequiredId, WithId } from "mongodb";
import { List } from "../../../../../Support/Share/Lib/Collections/List";
import { Entity } from "../../../../../Core/Module/Entity/Entity";
import { CoroutineLockComponent } from "../../../../../Core/Module/CoroutineLock/CoroutineLockComponent";
import { CoroutineLockType } from "../../../../../Core/Module/CoroutineLock/CoroutineLockType";
import { RandomGenerator } from "../../../../../Core/Helper/RandomGenerator";
import { Log } from "../../../../../Core/Module/Log/Log";


/**
 * 使用 Expand 扩展方法
 * 
 * 必须作出以下申明，才能调用对象的扩展方法
 */
declare module "../../../../Model/Server/Module/DB/DBComponent" {

    interface DBComponent {
        Connect(): Promise<DBComponent>;

        GetCollection<T extends Entity>(clazz: typeof Entity, collection?: string): Collection<T>;
        GetCollectionWith(name: string): Collection<Entity>;

        QueryWithId<T extends Entity>(clazz: typeof Entity, id: number, collection?: string): Promise<WithId<T>[]>;
        Query<T extends Entity>(clazz: typeof Entity, filter: Filter<T>, collection?: string): Promise<WithId<T>[]>;
        QueryWithTaskIdAndFilter<T extends Entity>(clazz: typeof Entity, taskId: number, filter: Filter<T>, collection?: string): Promise<WithId<T>[]>;
        QueryWithIdInCollections<T extends Entity>(clazz: typeof Entity, id: number, collectionNames: List<string>, result: List<T>): Promise<void>;

        InsertBatch<T extends Entity>(clazz: typeof Entity, list: OptionalUnlessRequiredId<T>[], collection?: string);

        Save<T extends Entity>(clazz: typeof Entity, entity: T, collection?: string): Promise<void>;
        SaveWithTaskId<T extends Entity>(clazz: typeof Entity, taskId: number, entity: T, collection?: string): Promise<void>;
        SaveList<T extends Entity>(clazz: typeof Entity, id: number, entities: List<Entity>): Promise<void>;
        SaveNotWait<T extends Entity>(clazz: typeof Entity, entity: T, taskId: number, collection?: string): Promise<void>;

        RemoveWithId<T extends Entity>(clazz: typeof Entity, id: number, collection?: string): Promise<number>;
        RemoveWithTaskId<T extends Entity>(clazz: typeof Entity, taskId: number, id: number, collection?: string): Promise<number>;
        Remove<T extends Entity>(clazz: typeof Entity, filter: Filter<T>, collection?: string): Promise<number>;
        RemoveWithTaskIdAndFilter<T extends Entity>(clazz: typeof Entity, taskId: number, filter: Filter<T>, collection?: string): Promise<number>;
    }

}

@ExpandOf(DBComponent)
export class DBComponentSystem {

    private static async Connect(self: DBComponent) {
        // console.log(`DBComponent Connect : ${self.dbConnection}:${self.dbName}`);
        self.mongoClient = await MongoClient.connect(self.dbConnection);
        self.database = self.mongoClient.db(self.dbName);
        return self;
    }

    private static GetCollection<T extends Entity>(self: DBComponent, clazz: typeof Entity, collection: string = null): Collection<T> {
        let result = self.database.collection<T>(collection ?? clazz.name);
        return result;
    }

    private static GetCollectionWith(self: DBComponent, name: string): Collection<Entity> {
        let result = self.database.collection<Entity>(name);
        return result;
    }

    //#region Query

    private static async QueryWithId<T extends Entity>(self: DBComponent, clazz: typeof Entity, id: number, collection: string = null): Promise<WithId<T>[]> {
        let coroutineLock = await CoroutineLockComponent.Instance.Wait(CoroutineLockType.DB, id % DBComponent.TaskCount);
        const cursor = await self.GetCollection<T>(clazz, collection).find(<any>{ Id: id });
        coroutineLock.Dispose();
        return cursor.toArray();
    }

    private static async Query<T extends Entity>(self: DBComponent, clazz: typeof Entity, filter: Filter<T>, collection: string = null): Promise<WithId<T>[]> {
        let coroutineLock = await CoroutineLockComponent.Instance.Wait(CoroutineLockType.DB, RandomGenerator.RandInt64() % DBComponent.TaskCount);
        const cursor = await self.GetCollection<T>(clazz, collection).find(filter);
        coroutineLock.Dispose();
        return cursor.toArray();
    }

    private static async QueryWithTaskIdAndFilter<T extends Entity>(self: DBComponent, clazz: typeof Entity, taskId: number, filter: Filter<T>, collection: string = null): Promise<WithId<T>[]> {
        let coroutineLock = await CoroutineLockComponent.Instance.Wait(CoroutineLockType.DB, taskId % DBComponent.TaskCount);
        const cursor = await self.GetCollection<T>(clazz, collection).find(filter);
        coroutineLock.Dispose();
        return cursor.toArray();
    }

    private static async QueryWithIdInCollections<T extends Entity>(self: DBComponent, clazz: typeof Entity, id: number, collectionNames: List<string>, result: List<T>): Promise<void> {
        if (collectionNames == null || collectionNames.Count == 0) {
            return;
        }
        let coroutineLock = await CoroutineLockComponent.Instance.Wait(CoroutineLockType.DB, id % DBComponent.TaskCount);
        for (let collectionName of collectionNames.ToArray()) {
            const cursor = await self.GetCollection<T>(clazz, collectionName).find(<any>{ Id: id });
            if (!cursor.hasNext()) {
                continue;
            }
            const e = await cursor.next();
            if (e == null) {
                continue;
            }
            // console.log(`DBComponentSystem.Query`);
            // console.log(e);
            result.Add(<unknown>e as T);
        }
        coroutineLock.Dispose();
    }

    //#endregion

    //#region Insert

    private static async InsertBatch<T extends Entity>(self: DBComponent, clazz: typeof Entity, list: OptionalUnlessRequiredId<T>[], collection: string = null): Promise<void> {
        if (collection == null) {
            collection = clazz.name;
        }
        let coroutineLock = await CoroutineLockComponent.Instance.Wait(CoroutineLockType.DB, RandomGenerator.RandInt64() % DBComponent.TaskCount);
        await self.GetCollection(clazz, collection).insertMany(ObjectList2BsonList(list));
        coroutineLock.Dispose();
    }

    //#endregion

    //#region Save

    private static async Save<T extends Entity>(self: DBComponent, clazz: typeof Entity, entity: T, collection: string = null): Promise<void> {
        if (entity == null) {
            Log.Error(`save entity is null`);
            return;
        }
        if (collection == null) {
            collection = GetTypeOf(entity);
        }
        let coroutineLock = await CoroutineLockComponent.Instance.Wait(CoroutineLockType.DB, entity.Id % DBComponent.TaskCount);
        await self.GetCollection(clazz, collection).replaceOne(Object2Bson({ Id: entity.Id }), Object2Bson(entity), { upsert: true });
        coroutineLock.Dispose();
    }

    private static async SaveWithTaskId<T extends Entity>(self: DBComponent, clazz: typeof Entity, taskId: number, entity: T, collection: string = null): Promise<void> {
        if (entity == null) {
            Log.Error(`save entity is null`);
            return;
        }
        if (collection == null) {
            collection = GetTypeOf(entity);
        }
        let coroutineLock = await CoroutineLockComponent.Instance.Wait(CoroutineLockType.DB, taskId % DBComponent.TaskCount);
        await self.GetCollection(clazz, collection).replaceOne(Object2Bson({ Id: entity.Id }), Object2Bson(entity), { upsert: true });
        coroutineLock.Dispose();
    }

    private static async SaveList(self: DBComponent, clazz: typeof Entity, id: number, entities: List<Entity>): Promise<void> {
        if (entities == null) {
            Log.Error(`save entity is null`);
            return;
        }
        let coroutineLock = await CoroutineLockComponent.Instance.Wait(CoroutineLockType.DB, id % DBComponent.TaskCount);
        for (let key in entities) {
            let entity = entities[key];
            if (entity == null) {
                continue;
            }
            const collection = GetTypeOf(entity);
            await self.GetCollection(clazz, collection).replaceOne(Object2Bson({ Id: entity.Id }), Object2Bson(entity), { upsert: true });
        }
        coroutineLock.Dispose();
    }

    private static async SaveNotWait<T extends Entity>(self: DBComponent, clazz: typeof Entity, entity: T, taskId: number = 0, collection: string = null): Promise<void> {
        if (taskId == 0) {
            await self.Save(clazz, entity, collection);
            return;
        }
        await self.SaveWithTaskId(clazz, taskId, entity, collection);
    }

    //#endregion

    //#region Remove

    private static async RemoveWithId<T extends Entity>(self: DBComponent, clazz: typeof Entity, id: number, collection: string = null): Promise<number> {
        let coroutineLock = await CoroutineLockComponent.Instance.Wait(CoroutineLockType.DB, id % DBComponent.TaskCount);
        const result = await self.GetCollection<T>(clazz, collection).deleteOne(<any>{ Id: id });
        coroutineLock.Dispose();
        return result.deletedCount;
    }

    private static async RemoveWithTaskId<T extends Entity>(self: DBComponent, clazz: typeof Entity, taskId: number, id: number, collection: string = null): Promise<number> {
        let coroutineLock = await CoroutineLockComponent.Instance.Wait(CoroutineLockType.DB, taskId % DBComponent.TaskCount);
        const result = await self.GetCollection<T>(clazz, collection).deleteOne(<any>{ Id: id });
        coroutineLock.Dispose();
        return result.deletedCount;
    }

    private static async Remove<T extends Entity>(self: DBComponent, clazz: typeof Entity, filter: Filter<T>, collection: string = null): Promise<number> {
        let coroutineLock = await CoroutineLockComponent.Instance.Wait(CoroutineLockType.DB, RandomGenerator.RandInt64() % DBComponent.TaskCount);
        const result = await self.GetCollection<T>(clazz, collection).deleteOne(filter);
        coroutineLock.Dispose();
        return result.deletedCount;
    }

    private static async RemoveWithTaskIdAndFilter<T extends Entity>(self: DBComponent, clazz: typeof Entity, taskId: number, filter: Filter<T>, collection: string = null): Promise<number> {
        let coroutineLock = await CoroutineLockComponent.Instance.Wait(CoroutineLockType.DB, taskId % DBComponent.TaskCount)
        const result = await self.GetCollection<T>(clazz, collection).deleteOne(filter);
        coroutineLock.Dispose();
        return result.deletedCount;
    }

}

@FriendOf(DBComponent)
@ObjectSystemAttribute()
export class DBComponentAwake3System extends Awake3System<DBComponent, string, string, number> {

    async Awake(self: DBComponent, dbConnection: string, dbName: string, zone: number) {
        self.dbConnection = dbConnection;
        self.dbName = dbName;
        self.zone = zone;
    }

}

@FriendOf(DBComponent)
@ObjectSystemAttribute()
export class DBComponentDestroySystem extends DestroySystem<DBComponent> {

    Destroy(self: DBComponent): void {

    }

}